/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.ide.maven.tools;

import org.eclipse.che.commons.xml.Element;
import org.eclipse.che.commons.xml.ElementMapper;
import org.eclipse.che.commons.xml.NewElement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.eclipse.che.commons.xml.NewElement.createElement;
import static org.eclipse.che.commons.xml.XMLTreeLocation.after;
import static org.eclipse.che.commons.xml.XMLTreeLocation.afterAnyOf;
import static org.eclipse.che.commons.xml.XMLTreeLocation.before;
import static org.eclipse.che.commons.xml.XMLTreeLocation.inTheBegin;
import static org.eclipse.che.commons.xml.XMLTreeLocation.inTheEnd;
import static java.lang.Boolean.parseBoolean;
import static java.util.Collections.emptyList;

/**
 * Describes <i>/project/build/resources/resource</i>, which contains information
 * about where associated with project files should be included.
 * <p/>
 * Supports next data:
 * <ul>
 * <li>targetPath</li>
 * <li>filtering</li>
 * <li>directory</li>
 * <li>includes</li>
 * <li>excludes</li>
 * </ul>
 *
 * @author Eugene Voevodin
 */
public class Resource {

    private static final ElementMapper<String> TEXT_MAPPER = new ElementTextMapper();

    private String       targetPath;
    private String       directory;
    private boolean      filtering;
    private List<String> includes;
    private List<String> excludes;

    Element resourceElement;

    public Resource() {
    }

    Resource(Element resourceElement) {
        this.resourceElement = resourceElement;
        targetPath = resourceElement.getChildText("targetPath");
        directory = resourceElement.getChildText("directory");
        filtering = parseBoolean(resourceElement.getChildText("filtering"));
        if (resourceElement.hasSingleChild("includes")) {
            includes = resourceElement.getSingleChild("includes").getChildren(TEXT_MAPPER);
        }
        if (resourceElement.hasSingleChild("excludes")) {
            excludes = resourceElement.getSingleChild("excludes").getChildren(TEXT_MAPPER);
        }
    }

    /**
     * Returns directory structure to place the set of resources from a build
     */
    public String getTargetPath() {
        return targetPath;
    }

    /**
     * Specifies the directory structure to place the set of resources from a build
     */
    public Resource setTargetPath(String targetPath) {
        this.targetPath = targetPath;
        if (!isNew()) {
            if (targetPath == null) {
                resourceElement.removeChild("targetPath");
            } else if (resourceElement.hasChild("targetPath")) {
                resourceElement.getSingleChild("targetPath").setText(targetPath);
            } else {
                resourceElement.insertChild(createElement("targetPath", targetPath), inTheBegin());
            }
        }
        return this;
    }

    /**
     * Returns directory where the resources are to be found
     */
    public String getDirectory() {
        return directory;
    }

    /**
     * Specifies directory where the resource are to be found
     */
    public Resource setDirectory(String directory) {
        this.directory = directory;
        if (!isNew()) {
            if (directory == null) {
                resourceElement.removeChild("directory");
            } else if (resourceElement.hasSingleChild("directory")) {
                resourceElement.getSingleChild("directory").setText(directory);
            } else {
                resourceElement.insertChild(createElement("directory", directory), afterAnyOf("targetPath",
                                                                                              "filtering").or(inTheBegin()));
            }
        }
        return this;
    }

    /**
     * Returns {@code true} if filtering is enabled for this resource, otherwise returns {@code false}
     */
    public boolean isFiltering() {
        return filtering;
    }

    public Resource setFiltering(boolean filtering) {
        this.filtering = filtering;
        if (!isNew()) {
            if (resourceElement.hasSingleChild("filtering")) {
                resourceElement.getSingleChild("filtering").setText(Boolean.toString(filtering));
            } else {
                resourceElement.insertChild(createElement("filtering", Boolean.toString(filtering)), after("targetPath").or(inTheBegin()));
            }
        }
        return this;
    }

    /**
     * Returns list of file patterns which specifies the files
     * to include into specified directory.
     */
    public List<String> getIncludes() {
        if (includes == null) {
            return emptyList();
        }
        return new ArrayList<>(includes);
    }

    /**
     * Specifies list of file patterns which specifies the files
     * to include into specified directory
     */
    public Resource setIncludes(Collection<String> includes) {
        if (includes == null || includes.isEmpty()) {
            removeIncludes();
        } else {
            setIncludes0(includes);
        }
        return this;
    }

    /**
     * Returns list of file patterns which specifies the files to exclude
     * from specified directory
     */
    public List<String> getExcludes() {
        if (excludes == null) {
            return emptyList();
        }
        return new ArrayList<>(excludes);
    }

    /**
     * Specifies list of file patterns which specifies the files
     * to exclude from specified directory
     */
    public Resource setExcludes(Collection<String> excludes) {
        if (excludes == null || excludes.isEmpty()) {
            removeExcludes();
        } else {
            setExcludes0(excludes);
        }
        return this;
    }


    NewElement asXMLElement() {
        final NewElement resource = createElement("resource");
        if (targetPath != null) {
            resource.appendChild(createElement("targetPath", targetPath));
        }
        if (filtering) {
            resource.appendChild(createElement("filtering", "true"));
        }
        if (directory != null) {
            resource.appendChild(createElement("directory", directory));
        }
        if (includes != null && !includes.isEmpty()) {
            resource.appendChild(newXMLElement(includes, "includes", "include"));
        }
        if (excludes != null && !excludes.isEmpty()) {
            resource.appendChild(newXMLElement(excludes, "excludes", "exclude"));
        }
        return resource;
    }

    private void setIncludes0(Collection<String> includes) {
        this.includes = new ArrayList<>(includes);

        if (isNew()) return;
        //if includes element exists we should replace it children
        //with new set of includes, otherwise create element for it
        if (resourceElement.hasSingleChild("includes")) {
            final Element includesElement = resourceElement.getSingleChild("includes");
            //remove all includes from element
            for (Element inclusion : includesElement.getChildren()) {
                inclusion.remove();
            }
            //append each new inclusion to "includes" element
            for (String inclusion : includes) {
                includesElement.appendChild(createElement("include", inclusion));
            }
        } else {
            resourceElement.insertChild(newXMLElement(this.includes, "includes", "include"), before("excludes").or(inTheEnd()));
        }
    }

    private void removeIncludes() {
        if (!isNew()) {
            resourceElement.removeChild("includes");
        }
        includes = null;
    }

    private void removeExcludes() {
        if (!isNew()) {
            resourceElement.removeChild("excludes");
        }
        excludes = null;
    }

    private void setExcludes0(Collection<String> excludes) {
        this.excludes = new ArrayList<>(excludes);

        if (isNew()) return;
        //if excludes element exists we should replace it children
        //with new set of excludes, otherwise create element for it
        if (resourceElement.hasSingleChild("excludes")) {
            final Element excludesElement = resourceElement.getSingleChild("excludes");
            //remove all exclusions from element
            for (Element exclusion : excludesElement.getChildren()) {
                exclusion.remove();
            }
            //append each new exclusion to "excludes" element
            for (String exclusion : excludes) {
                excludesElement.appendChild(createElement("exclude", exclusion));
            }
        } else {
            resourceElement.appendChild(newXMLElement(this.excludes, "excludes", "exclude"));
        }
    }

    private boolean isNew() {
        return resourceElement == null;
    }

    private NewElement newXMLElement(List<String> text, String parentName, String childName) {
        final NewElement element = createElement(parentName);
        for (String line : text) {
            element.appendChild(createElement(childName, line));
        }
        return element;
    }

    private static class ElementTextMapper implements ElementMapper<String> {

        @Override
        public String map(Element element) {
            return element.getText();
        }
    }
}
